﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Urb.Weather.Core;
using Urb.Weather.Core.Configuration;
using Urb.Weather.Core.Engine;
using Urb.Weather.Core.Manager;
using Urb.Weather.Core.MeasureUnit;
using Urb.Weather.Core.Provider;
using Urb.Weather.Core.UI;
using Urb.Weather.Core.Util;
using IContainer = Urb.Weather.Core.Container.IContainer;

namespace Urb.Weather.Ticker.UI
{
    public class MainViewModel : IMainViewModel
    {
        private IContainer container;
        private IManager manager;
        private IDispatcher dispatcher;
        private IConfigurationManager configurationManager;

        private decimal? currentTemperature = null;
        private ObservableCollection<TemperatureConditionComponentDataView> currentTemperatureComponents = new ObservableCollection<TemperatureConditionComponentDataView>();
        private decimal? minTemperature = null;
        private decimal? maxTemperature = null;
        private TemperatureUnit temperatureUnit = TemperatureUnit.Celsius;
        private DateTimeOffset? currentTemperatureMeasurementTime = null;
        private DateTimeOffset? minTemperatureMeasurementTime = null;
        private DateTimeOffset? maxTemperatureMeasurementTime = null;
        private bool anyTemperatureExtremeRecorded = false;

        private decimal? currentPressure = null;
        private ObservableCollection<PressureConditionComponentDataView> currentPressureComponents = new ObservableCollection<PressureConditionComponentDataView>();
        private decimal? minPressure = null;
        private decimal? maxPressure = null;
        private PressureUnit pressureUnit = PressureUnit.HPa;
        private DateTimeOffset? currentPressureMeasurementTime = null;
        private DateTimeOffset? minPressureMeasurementTime = null;
        private DateTimeOffset? maxPressureMeasurementTime = null;
        private bool anyPressureExtremeRecorded = false;

        private bool anyExtremeRecorded = false;

        private AtmosphereConditionsCode? currentAtmosphereConditionsCode = null;
        private string currentAtmosphereConditionsDescription = null;

        private ForecastDataView todayForecast;
        private ObservableCollection<ForecastDataView> nextDaysForecast = new ObservableCollection<ForecastDataView>();
        
        public MainViewModel(
            IContainer container,
            IDispatcher dispatcher,
            IManager manager,
            IConfigurationManager configurationManager)
        {
            this.container = container;
            this.dispatcher = dispatcher;
            this.manager = manager;

            this.manager.TemperatureCurrentConditionsChanged += manager_TemperatureCurrentConditionsChanged;
            this.manager.PressureCurrentConditionsChanged += manager_PressureCurrentConditionsChanged;
            this.manager.AtmosphereCurrentConditionsChanged += manager_AtmosphereCurrentConditionsChanged;

            this.manager.TemperatureExtremesChanged += new Action<TemperatureConditions, TemperatureConditions>(manager_TemperatureExtremesChanged);
            this.manager.PressureExtremesChanged += new Action<PressureConditions, PressureConditions>(manager_PressureExtremesChanged);

            this.manager.TodayForecastChanged += manager_TodayForecastChanged;
            this.manager.NextDaysForecastChanged += manager_NextDaysForecastChanged;

            this.manager.BeginLoadConfiguration();
        }

        public decimal? CurrentTemperature
        {
            get { return currentTemperature; }
            private set
            {
                currentTemperature = value;

                RaisePropertyChanged("CurrentTemperature");
            }
        }

        public ObservableCollection<TemperatureConditionComponentDataView> CurrentTemperatureComponents
        {
            get { return currentTemperatureComponents; }
        }

        public decimal? MinTemperature
        {
            get { return minTemperature; }
            private set
            {
                minTemperature = value;

                RaisePropertyChanged("MinTemperature");
            }
        }

        public decimal? MaxTemperature
        {
            get { return maxTemperature; }
            private set
            {
                maxTemperature = value;

                RaisePropertyChanged("MaxTemperature");
            }
        }

        public TemperatureUnit TemperatureUnit
        {
            get { return temperatureUnit; }
            private set
            {
                temperatureUnit = value;

                RaisePropertyChanged("TempeatureUnit");
            }
        }

        public DateTimeOffset? MinTemperatureMeasurementTime
        {
            get { return this.minTemperatureMeasurementTime; }
            private set
            {
                this.minTemperatureMeasurementTime = value;
                RaisePropertyChanged("MinTemperatureMeasurementTime");
            }
        }

        public DateTimeOffset? MaxTemperatureMeasurementTime
        {
            get { return this.maxTemperatureMeasurementTime; }
            private set
            {
                this.maxTemperatureMeasurementTime = value;
                RaisePropertyChanged("MaxTemperatureMeasurementTime");
            }
        }
        
        public bool AnyTemperatureExtremeRecorded
        {
            get { return anyTemperatureExtremeRecorded; }
            private set
            {
                if (anyTemperatureExtremeRecorded != value)
                {
                    anyTemperatureExtremeRecorded = value;
                    RaisePropertyChanged("AnyTemperatureExtremeRecorded");
                }
            }
        }

        public decimal? CurrentPressure
        {
            get { return currentPressure; }
            private set
            {
                currentPressure = value;

                RaisePropertyChanged("CurrentPressure");
            }
        }

        public ObservableCollection<PressureConditionComponentDataView> CurrentPressureComponents
        {
            get { return currentPressureComponents; }
        }

        public decimal? MinPressure
        {
            get { return minPressure; }
            private set
            {
                minPressure = value;

                RaisePropertyChanged("MinPressure");
            }
        }

        public decimal? MaxPressure
        {
            get { return maxPressure; }
            private set
            {
                maxPressure = value;

                RaisePropertyChanged("MaxPressure");
            }
        }

        public PressureUnit PressureUnit
        {
            get { return pressureUnit; }
            private set
            {
                pressureUnit = value;

                RaisePropertyChanged("TempeatureUnit");
            }
        }

        public DateTimeOffset? MinPressureMeasurementTime
        {
            get { return this.minPressureMeasurementTime; }
            private set
            {
                if (this.minPressureMeasurementTime != value)
                {
                    this.minPressureMeasurementTime = value;
                    RaisePropertyChanged("MinPressureMeasurementTime");
                }
            }
        }

        public DateTimeOffset? MaxPressureMeasurementTime
        {
            get { return this.maxPressureMeasurementTime; }
            private set
            {
                if (this.maxPressureMeasurementTime != value)
                {
                    this.maxPressureMeasurementTime = value;
                    RaisePropertyChanged("MaxPressureMeasurementTime");
                }
            }
        }

        public bool AnyPressureExtremeRecorded
        {
            get { return anyPressureExtremeRecorded; }
            private set
            {
                if (anyPressureExtremeRecorded != value)
                {
                    anyPressureExtremeRecorded = value;
                    RaisePropertyChanged("AnyPressureExtremeRecorded");
                }
            }
        }

        public bool AnyExtremeRecorded
        {
            get { return anyExtremeRecorded; }
            private set
            {
                if (anyExtremeRecorded != value)
                {
                    anyExtremeRecorded = value;
                    RaisePropertyChanged("AnyExtremeRecorded");
                }
            }
        }

        public AtmosphereConditionsCode? CurrentAtmosphereConditionsCode
        {
            get { return this.currentAtmosphereConditionsCode; }
            private set
            {
                if (currentAtmosphereConditionsCode != value)
                {
                    currentAtmosphereConditionsCode = value;
                    RaisePropertyChanged("CurrentAtmosphereConditionsCode");
                }
            }
        }

        public string CurrentAtmosphereConditionsDescription
        {
            get { return this.currentAtmosphereConditionsDescription; }
            private set
            {
                if (currentAtmosphereConditionsDescription != value)
                {
                    currentAtmosphereConditionsDescription = value;
                    RaisePropertyChanged("CurrentAtmosphereConditionsDescription");
                }
            }
        }

        public ForecastDataView TodayForecast
        {
            get { return todayForecast; }
        }

        public ObservableCollection<ForecastDataView> NextDaysForecast
        {
            get { return nextDaysForecast; }
        }

        private void manager_TemperatureCurrentConditionsChanged(AverageTemperatureConditions conditions)
        {
            if (conditions == null)
                return;

            dispatcher.Dispatch(
                () =>
                {
                    decimal measuredTemperature =
                                TemperatureHelper.ConvertTemperature(
                                    conditions.AverageValue,
                                    conditions.Unit,
                                    temperatureUnit);

                    this.CurrentTemperature = measuredTemperature;

                    this.currentTemperatureComponents.Clear();

                    foreach (var component in conditions.Components)
                    {
                        this.currentTemperatureComponents.Add(
                            new TemperatureConditionComponentDataView()
                            {
                                SourceName = component.SourceName,
                                MeasurementTime = component.MeasurementTime,
                                Value = this.temperatureUnit.ConvertTemperatureTo(component.Value, component.Unit),
                                Unit = this.temperatureUnit
                            }
                        );
                    }

                    // RaisePropertyChanged("CurrentTemperatureComponents");
                }
            );
        }

        private void manager_PressureCurrentConditionsChanged(AveragePressureConditions conditions)
        {
            if (conditions == null)
                return;

            dispatcher.Dispatch(
                () =>
                {
                    decimal measuredPressure =
                        PressureHelper.ConvertPressure(
                            conditions.AverageValue,
                            conditions.Unit,
                            pressureUnit);

                    this.CurrentPressure = measuredPressure;

                    this.currentPressureComponents.Clear();

                    foreach (var component in conditions.Components)
                    {
                        this.currentPressureComponents.Add(
                            new PressureConditionComponentDataView()
                            {
                                SourceName = component.SourceName,
                                MeasurementTime = component.MeasurementTime,
                                Value = this.pressureUnit.ConvertPressureTo(component.Value, component.Unit),
                                Unit = this.pressureUnit
                            }
                        );
                    }

                    // RaisePropertyChanged("CurrentPressureComponents");
                }
            );
        }

        private void manager_AtmosphereCurrentConditionsChanged(AtmosphereConditions atmosphereConditions)
        {
            dispatcher.Dispatch(
                () =>
                {
                    if (atmosphereConditions == null)
                    {
                        this.CurrentAtmosphereConditionsCode = null;
                        this.CurrentAtmosphereConditionsDescription = null;
                    }
                    else
                    {
                        this.CurrentAtmosphereConditionsCode = atmosphereConditions.Code;
                        this.CurrentAtmosphereConditionsDescription = atmosphereConditions.Description;
                    }
                }
            );
        }

        private void manager_TemperatureExtremesChanged(TemperatureConditions minConditions, TemperatureConditions maxConditions)
        {
            dispatcher.Dispatch(
                () =>
                {
                    decimal min = temperatureUnit.ConvertTemperatureTo(minConditions.Value, minConditions.Unit);
                    decimal max = temperatureUnit.ConvertTemperatureTo(maxConditions.Value, maxConditions.Unit);

                    this.MinTemperature = min;
                    this.MaxTemperature = max;
                    this.MinTemperatureMeasurementTime = minConditions.MeasurementTime;
                    this.MaxTemperatureMeasurementTime = maxConditions.MeasurementTime;

                    if (!this.AnyExtremeRecorded)
                        this.AnyExtremeRecorded = true;

                    if (!this.AnyTemperatureExtremeRecorded)
                        this.AnyTemperatureExtremeRecorded = true;
                }
            );
        }

        private void manager_PressureExtremesChanged(PressureConditions minConditions, PressureConditions maxConditions)
        {
            dispatcher.Dispatch(
                () =>
                {
                    decimal min = pressureUnit.ConvertPressureTo(minConditions.Value, minConditions.Unit);
                    decimal max = pressureUnit.ConvertPressureTo(maxConditions.Value, maxConditions.Unit);

                    this.MinPressure = min;
                    this.MaxPressure = max;
                    this.MinPressureMeasurementTime = minConditions.MeasurementTime;
                    this.MaxPressureMeasurementTime = maxConditions.MeasurementTime;

                    if (!this.AnyExtremeRecorded)
                        this.AnyExtremeRecorded = true;

                    if (!this.AnyPressureExtremeRecorded)
                        this.AnyPressureExtremeRecorded = true;
                }
            );
        }

        private void manager_TodayForecastChanged(Forecast forecast)
        {
            dispatcher.Dispatch(
                () =>
                {
                    if (forecast == null)
                    {
                        this.todayForecast = null;
                        RaisePropertyChanged("TodayForecast");

                        return;
                    }

                    this.todayForecast = new ForecastDataView()
                    {
                        DayOfWeek = forecast.ReferenceDate.LocalDateTime.AddDays(forecast.DayOffset).DayOfWeek,
                        MinTemperature = TemperatureHelper.ConvertTemperature(
                                            forecast.TemperatureMin,
                                            forecast.TemperatureUnit,
                                            temperatureUnit),
                        MaxTemperature = TemperatureHelper.ConvertTemperature(
                                            forecast.TemperatureMax,
                                            forecast.TemperatureUnit,
                                            temperatureUnit)
                    };

                    RaisePropertyChanged("TodayForecast");
                }
            );
        }

        private void manager_NextDaysForecastChanged(List<Forecast> forecasts)
        {
            dispatcher.Dispatch(
                () =>
                {
                    this.nextDaysForecast.Clear();

                    if (forecasts != null)
                    {
                        foreach (var forecast in forecasts)
                        {
                            if (forecast == null)
                                continue;

                            var forecastDataView = new ForecastDataView();

                            forecastDataView.DayOfWeek = forecast.ReferenceDate.LocalDateTime.AddDays(forecast.DayOffset).DayOfWeek;

                            forecastDataView.MaxTemperature =
                                TemperatureHelper.ConvertTemperature(
                                    forecast.TemperatureMax,
                                    forecast.TemperatureUnit,
                                    temperatureUnit);

                            forecastDataView.MinTemperature =
                                TemperatureHelper.ConvertTemperature(
                                    forecast.TemperatureMin,
                                    forecast.TemperatureUnit,
                                    temperatureUnit);

                            this.nextDaysForecast.Add(forecastDataView);
                        }
                    }

                    RaisePropertyChanged("NextDaysForecast");
                }
            );
        }
                    
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName)
        {
            dispatcher.Dispatch(
                () =>
                {
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            );
        }
    }
}
